/**
 * @(#)RegistrationDesk.java		
 * 
 * History:
 * 1.0	18 April 2012
 * 		Added behaviour to close the registration: CloseRegistration
 * 		Added behaviour to process the registration: ProcessRegistration
 * 2.0  30 April 2012
 * 		Added behaviour to send start race signal to Referee: SendStartRaceSignal
 * 2.1  2 May 2012
 * 		Moved from upm.pb4agent.common to upm.pb4agent.team1.platform
 * 		Extend AgBase1 instead of Agent
 * 		Combined CloseRegistration, ProcessRegistration, and SendStartRaceSignal into one behaviour class: ProcessRegistration.
 * 2.2 	7 May 2012
 * 		Add protocol to messages that involve the use of the ontology. 
 * 3.0	11 May 2012
 * 		Introducing team 1 ontology - StartRace
 * 4.0	18 May 2012
 * 		Changed StartRace into RegistrationClosed
 * 4.1	21 May 2012
 * 		Applied single Watcher per team
 */
package upm.pb4agent.team1.platform;

import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec.CodecException;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.wrapper.StaleProxyException;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import upm.pb4agent.ontology.Dock;
import upm.pb4agent.ontology.DockDesignation;
import upm.pb4agent.ontology.Registration;
import upm.pb4agent.team1.AgBase1;
import upm.pb4agent.team1.ontology.RegistrationClosed;
import upm.pb4agent.team1.team.Constant;
import upm.pb4agent.team1.util.AgentUtil;

/**
 * This class handles the registration process which main task is to receive
 * registration forms from managers and validate them. After the registration
 * period ends, it signals the referee to start the race.
 * 
 * @version 4.1
 * @author Bob Nicolaus
 * 
 */
public class RegistrationDesk extends AgBase1 {

	private static final long serialVersionUID = 6110888192554850312L;

	private jade.util.leap.List acceptedTeam = new jade.util.leap.ArrayList();

	@Override
	protected void setup() {

		System.out.println(getLocalName() + ": Enters the system");

		try {

			registerAgent(Constant.REGISTRATION_DESK_DF_TYPE);
		} catch (FIPAException e) {
			e.printStackTrace();
		}

		// Add behaviours
		addBehaviour(new ProcessRegistration(this));
	}

	/**
	 * This class handles the registration process.
	 * 
	 * @author Dave Rojas
	 * 
	 */
	class ProcessRegistration extends SimpleBehaviour {

		private static final long serialVersionUID = -5444735014847500036L;

		private boolean isRegistrationClosed = false;

		private long registrationPeriod = 0;
		private long registrationTime = 0;

		private List<AID> rowerList = null;

		public ProcessRegistration(Agent agent) {
			super(agent);

			try {

				registrationPeriod = Long.valueOf(AgentUtil
						.getProperty("registration_period"));
			} catch (NumberFormatException e) {
				e.printStackTrace();
			}

			registrationTime = new Date().getTime();
		}

		@Override
		public void action() {

			boolean isRegistrationFormValid = true;
			String teamID = "";
			Long currentTime;

			ACLMessage requestMessage = receive(MessageTemplate.and(
					MessageTemplate.and(standardTemplate, MessageTemplate
							.MatchPerformative(ACLMessage.REQUEST)),
					MessageTemplate
							.MatchProtocol(Constant.REGISTRATION_PROTOCOL)));

			if (isRegistrationClosed == false) {

				// Check if current time has passed the registration period
				currentTime = new Date().getTime();

				if (currentTime - registrationTime >= registrationPeriod) {

					// Registration is closed
					isRegistrationClosed = true;
					System.out.println(getLocalName()
							+ ": Registration is closed");

					// Inform the referee to be ready to start the race
					AID refereeAID = null;
					try {

						refereeAID = AgentUtil.searchAgentInDF(myAgent,
								Constant.REFEREE_DF_TYPE);

						RegistrationClosed registrationClosed = new RegistrationClosed();
						registrationClosed.setAcceptedTeam(acceptedTeam);
						Action action = new Action(refereeAID,
								registrationClosed);

						sendInternalMessage(ACLMessage.INFORM, refereeAID,
								action, Constant.REGISTRATION_CLOSED_PROTOCOL);
					} catch (FIPAException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					System.out.println(getLocalName()
							+ ": Send start race signal");
				}
			}

			if (requestMessage != null) {

				// Processing request
				try {

					ContentElement contentElement = getContentManager()
							.extractContent(requestMessage);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof Registration) {

							Registration registration = (Registration) concept;
							teamID = registration.getRegistrationForm()
									.getTeamID().getValue();

							// Check if registration is already closed
							if (isRegistrationClosed == false) {

								// Validate the registration form
								isRegistrationFormValid = validateRegistrationForm(registration
										.getRegistrationForm()
										.getDockDesignation());

								if (isRegistrationFormValid == true) {

									// Inform the sender
									replyMessage(requestMessage,
											ACLMessage.INFORM,
											Constant.REGISTRATION_PROTOCOL);
									System.out.println(getLocalName()
											+ ": Team " + teamID
											+ " is accepted");

									acceptedTeam.add(teamID);

									// Create watcher for this team
									createNewAgent("Watcher" + teamID,
											Watcher.class.getCanonicalName(),
											new Object[] { rowerList, teamID });
								}
							}
						} else {
							isRegistrationFormValid = false;
						}
					} else {
						isRegistrationFormValid = false;
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				} catch (StaleProxyException e) {
					e.printStackTrace();
				}

				if (isRegistrationClosed == true
						|| isRegistrationFormValid == false) {

					// Refuse the request
					replyMessage(requestMessage, ACLMessage.REFUSE,
							Constant.REGISTRATION_PROTOCOL);
					System.out.println(getLocalName() + ": " + teamID
							+ " is disqualified");
				}
			} else {

				block(1000);
			}
		}

		@Override
		public boolean done() {
			return isRegistrationClosed == true;
		}

		/**
		 * This method validates the registration form. A registration form is
		 * valid if there is no duplicate rower and no duplicate assigned dock
		 * inside.
		 * 
		 * @param dockDesignationList
		 *            list of Rower and Dock pair
		 * @return true if the registration form is valid with criteria: there
		 *         is no duplicate rower ID and no duplicate assigned dock
		 */
		private boolean validateRegistrationForm(
				jade.util.leap.List dockDesignationList) {

			boolean isValid = true;

			List<AID> rowerIDList = new ArrayList<AID>();
			List<String> dockNameList = new ArrayList<String>();

			AID rowerID = null;
			Dock dock = null;

			jade.util.leap.Iterator iterator = dockDesignationList.iterator();

			while (iterator.hasNext() == true) {
				DockDesignation dockDesignation = (DockDesignation) iterator
						.next();
				rowerID = dockDesignation.getRowerID();
				dock = dockDesignation.getDock();

				// Check if the ID is already in the list
				if (rowerIDList.contains(rowerID) == true) {
					isValid = false;
					break;
				} else {
					rowerIDList.add(rowerID);
				}

				// Check if the dock is already in the list
				if (dockNameList.contains(dock.getName()) == true) {
					isValid = false;
					break;
				} else {
					dockNameList.add(dock.getName());
				}
			}

			rowerList = rowerIDList;

			return isValid;
		}
	}
}
